using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using AntEater.ComponentModel;
using AntEater.Runtime;
using AntEater.Runtime.Services.Transformation;
using AntEater.Runtime.UI;


namespace AntEater.Integration.Editors
{
    /// <summary>
    /// Provides the Editor Pane for editing build files.
    /// </summary>
    [CLSCompliant(false)]
    public sealed class BuildFileEditorPane : UserControl, IVsWindowPane, IVsTextLinesEvents, IVsDeferredDocView
    {
        private ServiceProvider _serviceProvider;

        private string _filename;
        private IVsTextLines _textLines;
        private uint _textLinesEventCookie;

        private bool _isEditing;
        private bool _isCheckingFileEditable;

        private IAntEaterRuntime _runtime;
        private BuildFile _buildFile;
        private IBuildFilePresenter _buildFilePresenter;

        /// <summary>
        /// Initializes a new instance of the <see cref="BuildFileEditorPane"/> class.
        /// </summary>
        private BuildFileEditorPane()
            : this(null, null, null) {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BuildFileEditorPane"/> class.
        /// </summary>
        /// <param name="runtime">The runtime.</param>
        /// <param name="filename">The filename.</param>
        /// <param name="textLines">The text lines.</param>
        public BuildFileEditorPane(IAntEaterRuntime runtime, string filename, IVsTextLines textLines)
            : base() {

            this.InitializeComponent();

            this._runtime = runtime;
            this._textLines = textLines;
            this._filename = filename;
        }

        /// <summary>
        /// Initializes the build file.
        /// </summary>
        private void InitializeBuildFile() {

            // Check if a build file is set
            if ((_textLines != null) && (_filename != null)) {

                // Register the events
                this.RegisterEvents();

                // Create new build files
                _buildFile = new BuildFile(_filename);
                this.SetTextInBuildFile(this.GetTextOfTextLines());

                if (_buildFile.Definition != null) {
                    _buildFile.Definition.Changed += new EventHandler<EventArgs>(this.Changed);

                    this.InitializePresenter();
                }
            }
        }

        /// <summary>
        /// Initializes the presenter.
        /// </summary>
        private void InitializePresenter() {

            // Get presenter
            _buildFilePresenter = _runtime.Presenters.GetService(typeof(IBuildFilePresenter)) as IBuildFilePresenter;
            if (_buildFilePresenter != null) {

                // Set build file in presenter
                _buildFilePresenter.BuildFile = _buildFile;
                _buildFilePresenter.OpenFile += new EventHandler<BuildFileViewEventArgs>(PresenterOpenFile);

                // Get build file view
                Control control = _buildFilePresenter.View.Surface;
                control.Dock = DockStyle.Fill;
                this.Controls.Add(control);
            }
        }

        /// <summary>
        /// Refreshes the view.
        /// </summary>
        private void RefreshView() {
            if (_buildFilePresenter != null) {
                _buildFilePresenter.Refresh();
            }
        }

        /// <summary>
        /// Sets the text in build file.
        /// </summary>
        /// <param name="text">The text.</param>
        private void SetTextInBuildFile(string text) {

            // Get transformation service
            IXmlToBuildElementTransformationService service;
            service = _runtime.RuntimeServices.GetService(typeof(IXmlToBuildElementTransformationService)) as IXmlToBuildElementTransformationService;

            using (MemoryStream stream = new MemoryStream()) {
                // Put text in the new stream
                StreamWriter writer = new StreamWriter(stream);
                writer.Write(text);
                writer.Flush();

                // Move position to the beginning of the stream
                stream.Seek(0, SeekOrigin.Begin);

                // Transform the build file
                XmlToBuildElementTransformationResult result = service.Transform(stream, _buildFile.BuildFilePath);
                _buildFile.Definition = result.BuildFileDefinition;
                _buildFile.Messages.Clear();
                _buildFile.Messages.AddRange(result.Errors);
            }
        }

        /// <summary>
        /// Gets the text of build file.
        /// </summary>
        /// <returns></returns>
        private string GetTextOfBuildFile() {
            string text = String.Empty;

            // Get transformation service
            IBuildElementToXmlTransformationService service;
            service = _runtime.RuntimeServices.GetService(typeof(IBuildElementToXmlTransformationService)) as IBuildElementToXmlTransformationService;

            // Transform the build file
            BuildElementToXmlTransformationResult result = service.Transform(_buildFile.Definition, _buildFile.BuildFilePath);
            _buildFile.Messages.Clear();
            _buildFile.Messages.AddRange(result.Errors);
            using (StreamReader reader = new StreamReader(result.BuildFile)) {
                text = reader.ReadToEnd();
            }

            return text;
        }

        /// <summary>
        /// Sets the text in text lines.
        /// </summary>
        /// <param name="text">The text.</param>
        private void SetTextInTextLines(string text) {

            int lastLine;
            int lastColumn;

            // Get the end of the buffer
            _textLines.GetLastLineIndex(out lastLine, out lastColumn);
            IntPtr textPointer = Marshal.StringToCoTaskMemAuto(text);
            int length = text.Length;

            // Relpace the whole buffer
            _textLines.ReplaceLines(0, 0, lastLine, lastColumn, textPointer, length, null);
            Marshal.FreeCoTaskMem(textPointer);
        }

        /// <summary>
        /// Gets the text of text lines.
        /// </summary>
        /// <returns></returns>
        private string GetTextOfTextLines() {

            int lineCount;
            int lineLength;
            string text;

            // Get text aut of the buffer
            _textLines.GetLineCount(out lineCount);
            _textLines.GetLengthOfLine(lineCount - 1, out lineLength);
            _textLines.GetLineText(0, 0, lineCount - 1, lineLength, out text);

            return text;
        }

        /// <summary>
        /// Determines whether [is file editable].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is file editable]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsFileEditable() {

            bool isFileEditable = false;

            if (!_isCheckingFileEditable) {

                try {
                    _isCheckingFileEditable = true;

                    // Get Query Service
                    IVsQueryEditQuerySave2 query = (IVsQueryEditQuerySave2)_serviceProvider.GetService(typeof(SVsQueryEditQuerySave));

                    // Prepeare the parameters
                    string[] documents = { this._filename };
                    uint editable;
                    uint flags;

                    // Query the service for editing the file
                    int result = query.QueryEditFiles(0, documents.Length, documents, null, null, out editable, out flags);
                    if ((result == 0) && (editable == (uint)tagVSQueryEditResult.QER_EditOK)) {
                        isFileEditable = true;
                    }

                } finally {
                    _isCheckingFileEditable = false;
                }
            }

            return isFileEditable;
        }

        /// <summary>
        /// Registers the events.
        /// </summary>
        private void RegisterEvents() {

            // Register for change Events
            _textLines.AdviseTextLinesEvents(this, out _textLinesEventCookie);

            // Register buffer on the text manager
            IVsTextManager textManager = (IVsTextManager)_serviceProvider.GetService(typeof(SVsTextManager));
            if (textManager != null) {
                textManager.RegisterIndependentView(this, _textLines);
            }
        }

        /// <summary>
        /// Unregisters the events.
        /// </summary>
        private void UnregisterEvents() {

            // Unregsiter the change events
            _textLines.UnadviseTextLinesEvents(_textLinesEventCookie);

            // Unregeister buffer on the text manager
            IVsTextManager textManager = (IVsTextManager)_serviceProvider.GetService(typeof(SVsTextManager));
            if (textManager != null) {
                textManager.UnregisterIndependentView(this, _textLines);
            }
        }

        /// <summary>
        /// Changeds the specified sender.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void Changed(object sender, EventArgs e) {

            try {
                // Check if file is editable
                if (this.IsFileEditable()) {

                    // Set new text in buffer
                    _isEditing = true;
                    this.SetTextInTextLines(this.GetTextOfBuildFile());
                    _isEditing = false;
                }
            } catch (Exception ex) {
                Utilities.ShowException(_serviceProvider, ex, true);
            }
        }

        /// <summary>
        /// Presenters the open file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="AntEater.Runtime.UI.BuildFileViewEventArgs"/> instance containing the event data.</param>
        private void PresenterOpenFile(object sender, BuildFileViewEventArgs e) {
            SolutionManager.GetInstance().OpenFile(e.BuildFilePath, e.View);
        }

        /// <summary>
        /// Creates the pane window.
        /// </summary>
        /// <param name="hwndParent">The HWND parent.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="cx">The cx.</param>
        /// <param name="cy">The cy.</param>
        /// <param name="hwnd">The HWND.</param>
        /// <returns></returns>
        int IVsWindowPane.CreatePaneWindow(IntPtr hwndParent, int x, int y, int cx, int cy, out IntPtr hwnd) {

            // Initialize pane
            this.InitializeBuildFile();
            this.RefreshView();

            // Set parent pane
            Win32Methods.SetParent(base.Handle, hwndParent);
            hwnd = base.Handle;

            // Set size of pane
            base.Size = new Size(cx - x, cy - y);

            return VSConstants.S_OK;
        }

        /// <summary>
        /// Gets the size of the default.
        /// </summary>
        /// <param name="pSize">Size of the p.</param>
        /// <returns></returns>
        int IVsWindowPane.GetDefaultSize(SIZE[] pSize) {
            if (pSize.Length >= 1) {
                pSize[0].cx = base.Size.Width;
                pSize[0].cy = base.Size.Height;
            }

            return VSConstants.S_OK;
        }

        /// <summary>
        /// Sets the site.
        /// </summary>
        /// <param name="psp">The PSP.</param>
        /// <returns></returns>
        int IVsWindowPane.SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp) {
            _serviceProvider = new ServiceProvider(psp);
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Closes the pane.
        /// </summary>
        /// <returns></returns>
        int IVsWindowPane.ClosePane() {
            this.Dispose(true);
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Translates the accelerator.
        /// </summary>
        /// <param name="lpmsg">The LPMSG.</param>
        /// <returns></returns>
        int IVsWindowPane.TranslateAccelerator(MSG[] lpmsg) {
            return VSConstants.S_FALSE;
        }

        /// <summary>
        /// Saves the state of the view.
        /// </summary>
        /// <param name="pStream">The p stream.</param>
        /// <returns></returns>
        int IVsWindowPane.SaveViewState(IStream pStream) {
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Loads the state of the view.
        /// </summary>
        /// <param name="pStream">The p stream.</param>
        /// <returns></returns>
        int IVsWindowPane.LoadViewState(IStream pStream) {
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Called when [change line attributes].
        /// </summary>
        /// <param name="iFirstLine">The i first line.</param>
        /// <param name="iLastLine">The i last line.</param>
        void IVsTextLinesEvents.OnChangeLineAttributes(int iFirstLine, int iLastLine) {
        }

        /// <summary>
        /// Called when [change line text].
        /// </summary>
        /// <param name="pTextLineChange">The p text line change.</param>
        /// <param name="fLast">The f last.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        void IVsTextLinesEvents.OnChangeLineText(TextLineChange[] pTextLineChange, int fLast) {
            try {
                if (!_isEditing) {
                    this.RefreshView();
                }
            } catch (Exception exception) {
                Utilities.ShowException(_serviceProvider, exception, true);
            }
        }

        /// <summary>
        /// Get_s the CMD UI GUID.
        /// </summary>
        /// <param name="pGuidCmdId">The p GUID CMD id.</param>
        /// <returns></returns>
        int IVsDeferredDocView.get_CmdUIGuid(out Guid pGuidCmdId) {
            pGuidCmdId = Guids.BuildFileEditorFactoryIdGuid;
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Get_s the doc view.
        /// </summary>
        /// <param name="ppUnkDocView">The pp unk doc view.</param>
        /// <returns></returns>
        int IVsDeferredDocView.get_DocView(out IntPtr ppUnkDocView) {
            ppUnkDocView = Marshal.GetIUnknownForObject(this);
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Disposes the specified disposing.
        /// </summary>
        /// <param name="disposing">if set to <c>true</c> [disposing].</param>
        protected override void Dispose(bool disposing) {
            if (disposing) {
                if (_textLines != null) {
                    this.UnregisterEvents();
                    _textLines = null;
                }
            }

            base.Dispose(disposing);
        }

        /// <summary>
        /// Initializes the component.
        /// </summary>
        private void InitializeComponent() {
            this.SuspendLayout();
            // 
            // BuildFileEditorPane
            // 
            this.Name = "BuildFileEditorPane";
            this.Size = new System.Drawing.Size(300, 300);
            this.ResumeLayout(false);
        }
    }

}
